#if defined(_MSC_VER)
#pragma once
#endif

#ifndef LM_CORE_GEOMETRY_H
#define LM_CORE_GEOMETRY_H

#include "lm.h"
#include "vector2.h"
#include "vector3.h"
#include "vector4.h"
#include "point.h"
#include "ray.h"
#include "matrix.h"

using std::ostream;

// Geometry inline functions

// Vector2 inline functions
inline Vector2::Vector2(const Point2 &p) {
	x = p.x; y = p.y;
}
inline Point2 Vector2::operator+(const Point2 &p) const {
	return Point2(x+p.x, y+p.y);
}
// override ostream, make it easy to print information.
inline ostream& operator<<(ostream& out, Vector2 &v) {
	out << "Vector2 " << "[" << v.x << ", " << v.y << "]";
	return out;
}
inline Vector2 operator*(float f, const Vector2 &v) {
	return Vector2(v.x*f, v.y*f);
}

inline float Dot(const Vector2 &v1, const Vector2 &v2) {
	return v1.x*v2.x + v1.y*v2.y;
}
inline float AbsDot(const Vector2 &v1, const Vector2 &v2) {
	return abs(v1.x*v2.x + v1.y*v2.y);
}
inline Vector2 Normalize(const Vector2 &v) {
	return v / v.Length();
}

// Vector3 inline functions
inline Vector3::Vector3(const Point3 &p) {
	x = p.x; y = p.y; z = p.z;
}
inline Point3 Vector3::operator+(const Point3 &p) const {
	return Point3(x+p.x, y+p.y, z+p.z);
}
inline Vector3::Vector3(const Normal &n) {
	x = n.x; y = n.y; z = n.z;
}
inline ostream& operator<<(ostream& out, Vector3 &v) {
	out << "Vector3 " << "[" << v.x << ", " << v.y << ", "<< v.z << "]";
	return out;
}
inline Vector3 operator*(float f, const Vector3 &v) {
	return Vector3(v.x*f, v.y*f, v.z*f);
}
inline float Dot(const Vector3 &v1, const Vector3 &v2) {
	return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
}
inline float AbsDot(const Vector3 &v1, const Vector3 &v2) {
	return abs(v1.x*v2.x + v1.y*v2.y + v1.z*v2.z);
}
inline Vector3 Normalize(const Vector3 &v) {
	return v / v.Length();
}

// Vector4 inline functions
inline Vector4::Vector4(const Vector3 &v) {
	x = v.x; y = v.y; z  = v.z; w = 0;
}
inline Vector4::Vector4(const Normal &n) {
	x = n.x; y = n.y; z = n.z; w = 0;
}
inline Vector4::Vector4(const Point3 &p) {
	x = p.x; y = p.y; z = p.z; w = 1;
}
inline ostream& operator<<(ostream& out, Vector4 &v) {
	out << "Vector4 " << "[" << v.x << ", " << v.y << ", "<< v.z <<", "<< v.w << "]";
	return out;
}
inline Vector4 operator*(float f, const Vector4 &v) {
	return Vector4(v.x*f, v.y*f, v.z*f, v.w*f);
}

inline float Dot(const Vector4 &v1, const Vector4 &v2) {
	return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z + v1.w*v2.w;
}
inline float AbsDot(const Vector4 &v1, const Vector4 &v2) {
	return abs(v1.x*v2.x + v1.y*v2.y + v1.z*v2.z + v1.w*v2.w);
}
inline Vector4 Normalize(const Vector4 &v) {
	return v / v.Length();
}

// Normal inline functions
inline ostream& operator<<(ostream& out, Normal &n) {
	out << "Normal " << "[" << n.x << ", " << n.y << ", " << n.z << "]";
	return out;
}
inline Point3 Normal::operator+(const Point3 &p) const {
	return Point3(x+p.x, y+p.y, z+p.z);
}
inline Normal operator*(float f, const Normal &n) {
	return Normal(n.x*f, n.y*f, n.z*f);
}
inline Normal Normalize(const Normal &n) {
	return n / n.Length();
}

inline Normal::Normal(const Vector3 &v) {
	x = v.x; y = v.y; z = v.z;
}
inline Normal::Normal(const Vector4 &v) {
	x = v.x; y = v.y; z = v.z;	// discard w
}
inline Normal::Normal(const Point3 &p) {
	x = p.x; y = p.y; z = p.z;	// o->p
}

inline float Dot(const Normal &n1, const Normal &n2) {
	return n1.x * n2.x + n1.y * n2.y + n1.z * n2.z;
}
inline float AbsDot(const Normal &n1, const Normal &n2) {
	return abs(n1.x * n2.x + n1.y * n2.y + n1.z * n2.z);
}
inline float Dot(const Normal &n, const Vector3 &v) {
	return n.x * v.x + n.y * v.y + n.z * v.z;
}
inline float AbsDot(const Normal &n, const Vector3 &v) {
	return abs(n.x * v.x + n.y * v.y + n.z * v.z);
}
inline float Dot(const Vector3 &v, const Normal &n) {
	return n.x * v.x + n.y * v.y + n.z * v.z;
}
inline float AbsDot(const Vector3 &v, const Normal &n) {
	return abs(n.x * v.x + n.y * v.y + n.z * v.z);
}
inline Point2::Point2(const Vector2 &v) {
	x = v.x; y = v.y;
}
inline Point2 operator*(float f, const Point2 &p) {
	return Point2(p.x*f, p.y*f);
}
inline ostream& operator<<(ostream& out, Point2 &p) {
	out << "Point2 " << "[" << p.x << ", " << p.y << "]";
	return out;
}

inline Point3::Point3(const Vector3 &v) {
	x = v.x; y = v.y; z = v.z;
}
inline Point3 operator*(float f, const Point3 &p) {
	return Point3(p.x*f, p.y*f, p.z*f);
}
inline ostream& operator<<(ostream& out, Point3 &p) {
	out << "Point3 " << "[" << p.x << ", " << p.y << ", " << p.z << "]";
	return out;
}

// Cross Vector3|Normal
inline Vector3 Cross(const Vector3 &v1, const Vector3 &v2) {
	/* Left-handed vector cross product */
	return Vector3(
		(v1.y * v2.z) - (v1.z * v2.y), 
		(v1.z * v2.x) - (v1.x * v2.z),
		(v1.x * v2.y) - (v1.y * v2.x)
		);
}
inline Vector3 Cross(const Normal &n, const Vector3 &v) {
	/* Left-handed vector cross product */
	return Vector3(
		(n.y * v.z) - (n.z * v.y), 
		(n.z * v.x) - (n.x * v.z),
		(n.x * v.y) - (n.y * v.x)
		);
}
inline Vector3 Cross(const Vector3 &v, const Normal &n) {
	/* Left-handed vector cross product */
	return Vector3(
		(v.y * n.z) - (v.z * n.y), 
		(v.z * n.x) - (v.x * n.z),
		(v.x * n.y) - (v.y * n.x)
		);
}
inline Vector3 Cross(const Normal &n, const Normal &v) {
	/* Left-handed vector cross product */
	return Vector3(
		(n.y * v.z) - (n.z * v.y), 
		(n.z * v.x) - (n.x * v.z),
		(n.x * v.y) - (n.y * v.x)
		);
}
// Construct Local Coordinate
inline void CoordinateSystem(const Vector3 &v1, Vector3 *v2, Vector3 *v3) {
	if (fabsf(v1.x) > fabsf(v1.y)) {
		float invLen = 1.f / sqrtf(v1.x*v1.x + v1.z*v1.z);
		*v2 = Vector3(-v1.z * invLen, 0.f, v1.x * invLen);
	}
	else {
		float invLen = 1.f / sqrtf(v1.y*v1.y + v1.z*v1.z);
		*v2 = Vector3(0.f, v1.z * invLen, -v1.y * invLen);
	}
	*v3 = Cross(v1, *v2);
}
inline void CoordinateSystem(const Normal &n, Vector3 *v2, Vector3 *v3) {
	if (fabsf(n.x) > fabsf(n.y)) {
		float invLen = 1.f / sqrtf(n.x*n.x + n.z*n.z);
		*v2 = Vector3(-n.z * invLen, 0.f, n.x * invLen);
	}
	else {
		float invLen = 1.f / sqrtf(n.y*n.y + n.z*n.z);
		*v2 = Vector3(0.f, n.z * invLen, -n.y * invLen);
	}
	*v3 = Cross(n, *v2);
}
inline float DistanceSqured(const Vector2 &v1, const Vector2 &v2) {
	return (v1.x - v2.x) * (v1.x - v2.x) + (v1.y - v2.y) * (v1.y - v2.y);
}
inline float Distance(const Point2 &p1, const Point2 &p2) {
	return (p1 - p2).Length();
}
inline float DistanceSqured(const Point2 &p1, const Point2 &p2) {
	return (p1 - p2).LengthSqured();
}
inline float Distance(const Point3 &p1, const Point3 &p2) {
	return (p1 - p2).Length();
}
inline float DistanceSqured(const Point3 &p1, const Point3 &p2) {
	return (p1 - p2).LengthSqured();
}

inline Vector3 SphericalDirection(float sintheta,
	float costheta, float phi) {
		return Vector3(sintheta * cosf(phi),
			sintheta * sinf(phi),
			costheta);
}


inline Vector3 SphericalDirection(float sintheta, float costheta,
	float phi, const Vector3 &x,
	const Vector3 &y, const Vector3 &z) {
		return sintheta * cosf(phi) * x +
			sintheta * sinf(phi) * y + costheta * z;
}

#endif	// LM_CORE_GEOMETRY_H